package com.example.test_1_21.mqserver.datacenter;

import com.example.test_1_21.common.BinaryTool;
import com.example.test_1_21.common.MQException;
import com.example.test_1_21.mqserver.core.MSGQueue;
import com.example.test_1_21.mqserver.core.Message;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

//针对硬盘上的消息进行管理
public class MessageFileManager {
    //定义一个内部类，表示该队列的统计信息
    static public class Stat{
        public int totalCount;  //总消息数
        public int validCount;  //有效消息数
    }

    public void init(){}

    //约定消息文件所在的目录和文件名
    //用来获取到指定队列对应的消息文件所在路径
    private String getQueueDir(String queueName){
        return "./data/"+queueName;
    }

    //这个方法用来获取该队列的消息数据文件路径
    private String getQueueDataPath(String queueName){
        return getQueueDir(queueName)+"/queue_data.txt";
    }

    //用来获取该队列的消息统计文件路径
    private String getQueueStatPath(String queueName){
        return getQueueDir(queueName)+"./queue_stat.txt";
    }

    private Stat readStat(String queueName){
        Stat stat=new Stat();
        try (InputStream inputStream=new FileInputStream(getQueueStatPath(queueName))){
            Scanner sc=new Scanner(inputStream);
            stat.totalCount=sc.nextInt();
            stat.validCount=sc.nextInt();
            return stat;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void writeStat(String queueName,Stat stat){
        try (OutputStream outputStream=new FileOutputStream(getQueueStatPath(queueName))){
            PrintWriter printWriter=new PrintWriter(outputStream);
            printWriter.write(stat.totalCount+"\t"+stat.validCount);
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //创建队列对应的文件和目录
    public void createQueueFiles(String queueName) throws IOException {
        //1、先创建队列对应的消息目录
        File baseDir=new File(getQueueDir(queueName));
        if(!baseDir.exists()){
            boolean ok=baseDir.mkdirs();
            if(!ok){
                throw new IOException("创建目录失败！baseDir="+baseDir.getAbsolutePath());
            }
        }
        //2、创建队列数据文件
        File queueDataFile=new File(getQueueDataPath(queueName));
        if(!queueDataFile.exists()){
            boolean ok=queueDataFile.createNewFile();
            if(!ok){
                throw new IOException("创建文件失败！queueDataFile="+queueDataFile.getAbsolutePath());
            }
        }
        //3、创建消息统计文件
        File queueStatFile=new File(getQueueStatPath(queueName));
        if(!queueStatFile.exists()){
            boolean ok=queueStatFile.createNewFile();
            if(!ok){
                throw new IOException("创建文件失败！queueStatFile="+queueStatFile.getAbsolutePath());
            }
        }
        //4、给消息统计文件，设定初始值：0\t0
        Stat stat=new Stat();
        stat.totalCount=0;
        stat.validCount=0;
        writeStat(queueName,stat);
    }

    //删除队列的目录和文件
    //队列也是可以被删除的，当队列删除后，对应的消息文件也要随之删除
    public void destroyQueueFiles(String queueName) throws IOException {
        File queueDataFile=new File(getQueueDataPath(queueName));
        boolean ok1=queueDataFile.delete();
        File queueStatFile=new File(getQueueStatPath(queueName));
        boolean ok2=queueStatFile.delete();
        File baseDir=new File(getQueueDir(queueName));
        boolean ok3=baseDir.delete();
        if(!ok1||!ok2||!ok3){
            throw new IOException("删除队列目录和文件失败！baseDir="+baseDir.getAbsolutePath());
        }
    }

    //检查队列的目录和文件是否存在
    public boolean checkFileExists(String queueName){
        File queueDataFile=new File(getQueueDataPath(queueName));
        if(!queueDataFile.exists()){
            return false;
        }
        File queueStatFile=new File(getQueueStatPath(queueName));
        if(!queueStatFile.exists()){
            return false;
        }
        return true;
    }

    //用来把一个新的消息，放到队列对应的文件中
    public void sendMessage(MSGQueue queue, Message message) throws MQException, IOException {
        //1、检查一下当前要写入的队列对应的文件是否存在
        if(!checkFileExists(queue.getName())){
            throw new MQException("[MessageFileManager] 队列对应的文件不存在！queueName="+queue.getName());
        }
        //2、把Message对象进行序列化，转换成二进制的字节数组
        byte[] messageBinary= BinaryTool.toBytes(message);
        synchronized (queue) {
            //3、先获取到当前队列的长度，用这个来计算出Message对象的offsetBeg和offEnd
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            message.setOffsetBeg(queueDataFile.length() + 4);
            message.setOffsetEnd(queueDataFile.length() + 4 + messageBinary.length);
            //4、写入消息到数据文件
            try (OutputStream outputStream = new FileOutputStream(queueDataFile, true)) {
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    dataOutputStream.writeInt(messageBinary.length);
                    dataOutputStream.write(messageBinary);
                }
            }
            //5、更新消息统计文件
            Stat stat = readStat(queue.getName());
            stat.totalCount += 1;
            stat.validCount += 1;
            writeStat(queue.getName(), stat);
        }
    }

    //删除消息，逻辑删除，将isValid属性设置为0
    public void deleteMessage(MSGQueue queue,Message message) throws IOException, ClassNotFoundException {
        synchronized (queue){
            try(RandomAccessFile randomAccessFile=new RandomAccessFile(getQueueDataPath(queue.getName()),"rw")){
                //1、先从文件中读取对应的Message数据
                byte[] bufferSrc=new byte[(int) (message.getOffsetEnd()-message.getOffsetBeg())];
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.read(bufferSrc);
                //2、把当前读出来的二进制数据，转换成Message对象
                Message diskMessage=(Message) BinaryTool.fromBytes(bufferSrc);
                //3、把isValid设置为无效
                diskMessage.setIsValid((byte)0x0);
                //4、重新写入文件
                byte[] bufferDest=BinaryTool.toBytes(diskMessage);
                randomAccessFile.seek(message.getOffsetBeg());
                randomAccessFile.write(bufferDest);
            }
            //更新统计文件
            Stat stat=readStat(queue.getName());
            if(stat.validCount>0){
                stat.validCount-=1;
            }
            writeStat(queue.getName(),stat);
        }
    }

    //从文件中读取所有的消息内容，加载到内存中
    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws MQException, IOException, ClassNotFoundException {
        LinkedList<Message> messages=new LinkedList<>();
        try (InputStream inputStream=new FileInputStream(getQueueDataPath(queueName))){
            try (DataInputStream dataInputStream=new DataInputStream(inputStream)){
                long currentOffset=0;
                while (true){
                    //1、读取当前消息的长度
                    int messageSize=dataInputStream.readInt();
                    //2、按照这个长度读取消息内容
                    byte[] buffer=new byte[messageSize];
                    int actualSize=dataInputStream.read(buffer);
                    if(messageSize!=actualSize){
                        throw new MQException("[MessageFileManager] 文件格式错误！queueName="+queueName);
                    }
                    //3、把这个读到的二进制数据，反序列化回Message对象
                    Message message=(Message) BinaryTool.fromBytes(buffer);
                    //4、判断这个消息对象是不是无效对象
                    if(message.getIsValid()!=0x1){
                        currentOffset+=(4+messageSize);
                        continue;
                    }
                    //5、有效数据则需要把Message对象加入到链表中，加入之前还需要填写offsetBeg和offsetEnd
                    message.setOffsetBeg(currentOffset+4);
                    message.setOffsetEnd(currentOffset+4+messageSize);
                    currentOffset+=(4+messageSize);
                    messages.add(message);
                }
            }catch (EOFException e){
                //这个catch并非是处理异常，二十处理资产的个业务逻辑，文件读取到末尾，会被readInt抛出异常
                System.out.println("[MessageFileManager]恢复Message数据完成！");
            }
        }
        return messages;
    }

    //检查当前是否要针对该队列的消息数据文件进行GC
    public boolean checkGC(String queueName){
        Stat stat=readStat(queueName);
        if(stat.totalCount>2000&&(double)stat.validCount/(double)stat.totalCount<0.5 ){
            return true;
        }
        return false;
    }

    private String getQueueDataNewPath(String queueName){
        return getQueueDir(queueName)+"/queue_data_new.txt";
    }

    //执行消息数据文件的垃圾回收操作
    public void gc(MSGQueue queue) throws MQException, IOException, ClassNotFoundException {
        //进行GC时，是针对消息数据文件的操作。此时其他线程不能对该队列进行修改
        synchronized (queue){
            long gcBeg=System.currentTimeMillis();

            //1、创建一个新的文件
            File queueDataNewFile=new File(getQueueDataNewPath(queue.getName()));
            if(queueDataNewFile.exists()){
                //正常情况下这个文件不应该存在，如果存在就说明上次gc了一半程序出现意外
                throw new MQException("[MessageFileManager] GC时发现该队列的queue_data_new已经存在了 queueName="+queue.getName());
            }
            boolean ok=queueDataNewFile.createNewFile();
            if(!ok){
                throw new MQException("[MessageFileManager] 创建文件失败！queueDataNewFile="+queueDataNewFile.getAbsolutePath());
            }
            //2、从旧文件中，读取所有有效消息对象
            LinkedList<Message> messages=loadAllMessageFromQueue(queue.getName());
            //3、把有效消息，写入到新的文件中
            try (OutputStream outputStream=new FileOutputStream(queueDataNewFile)){
                try (DataOutputStream dataOutputStream=new DataOutputStream(outputStream)){
                    for(Message message:messages){
                        byte[] buffer=BinaryTool.toBytes(message);
                        dataOutputStream.writeInt(buffer.length);
                        dataOutputStream.write(buffer);
                    }
                }
            }
            //4、删除旧的数据文件，重命名新的文件
            File queueDataOldFile=new File(getQueueDataPath(queue.getName()));
            ok=queueDataOldFile.delete();
            if(!ok){
                throw new MQException("[MessageFileManager] 删除旧的数据文件失败！queueDataOldFile="+queueDataOldFile.getAbsolutePath());
            }
            ok=queueDataNewFile.renameTo(queueDataOldFile);
            if(!ok){
                throw new MQException("[MessageFileManager] 文件重命名失败！queueDataOldFile="+queueDataOldFile.getAbsolutePath()+",queueDataOldFile="+queueDataOldFile.getAbsolutePath());
            }
            //5、统计更新文件
            Stat stat=readStat(queue.getName());
            stat.totalCount=messages.size();
            stat.validCount=messages.size();
            writeStat(queue.getName(),stat);

            long gcEnd=System.currentTimeMillis();
            System.out.println("[MessageFileManeger] GC执行完毕！queueName="+queue.getName()+",time="+(gcEnd-gcBeg)+"ms");
        }
    }
}
